package rpc

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"pm/config"
	"pm/db"
	process2 "pm/process"
	"pm/util"
	"strconv"
	"strings"
	"time"
	"log"
	"gorm.io/gorm"
	"pm/model"
	"github.com/rcrowley/go-metrics"
	"github.com/smallnest/rpcx/client"
	"github.com/smallnest/rpcx/protocol"
	"github.com/smallnest/rpcx/server"
	"github.com/smallnest/rpcx/serverplugin"
	"github.com/smallnest/rpcx/share"
)

const (
	Port       = 7707
	UploadPort = 7708
	ServerName = "pmon2"
	UploadPath = "pmon2_upload"
)

type packageServer struct {
}

// 返回的数据结构
type DataOutput struct {
	Code    int         `json:"code"`
	Body    interface{} `json:"body,omitempty"`
	Message interface{} `json:"message,omitempty"`
}

type UploadPackInput struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

//包上传
func (s *packageServer) UploadPack(conn net.Conn, args *share.FileTransferArgs) {
	//获取参数
	var InputData UploadPackInput

	if err := json.Unmarshal([]byte(args.Meta["body"]), &InputData); err != nil {
		log.Print("UploadPack 参数错误-" + err.Error())
	}
	if InputData.Version == "" || InputData.Name == ""{
		conn.Close()
		return
	}

	var exists model.PackageHistory
	err2 := db.NewDb().Where("version = ? AND name = ?", InputData.Version, InputData.Name).First(&exists).Error
	if err2 == nil {
		conn.Close()
		return
	}
	//文件转移
	var err1 error
	Data, _ := ioutil.ReadAll(conn)
	conn.Close()
	bakPath := config.ConfigData.PackagesBakPath
	if bakPath == "" {
		fp, _ := util.FileBasePath()
		fp = filepath.Dir(fp)
		bakPath = fp + string(os.PathSeparator) + "packages"
	}
	util.FileCreateFile(bakPath)
	filePath := bakPath + string(os.PathSeparator) + InputData.Name + "." + InputData.Version
	err1 = ioutil.WriteFile(filePath, Data, 0644)
	if err1 != nil {
		log.Print("UploadPack 备份文件失败-" + err1.Error())
	}
	newPath,_ := filepath.Abs(filePath)
	db.NewDb().Create(&model.PackageHistory{
		Name:     InputData.Name,
		Version:  InputData.Version,
		Status:   1,
		FilePath: newPath,
	})

	//只备份前三个版本的包
	var packageList []model.PackageHistory
	db.NewDb().Model(&model.PackageHistory{}).
		Where("name = ?", InputData.Name).
		Order("created_at desc").
		Find(&packageList)
	for k, item := range packageList {
		if k >= 3 && item.Status < 3 {
			err := os.Remove(bakPath + "/" + InputData.Name + "." + item.Version)
			if err == nil {
				item.Status = 3
				db.NewDb().Model(&model.PackageHistory{}).Where("ID = ?", item.ID).Updates(&item)
			}
		}
	}
}

type PackageListInput struct {
	Page     int    `json:"page,omitempty"`
	PageSize int    `json:"page_size,omitempty"`
	Name     string `json:"name,omitempty"`
	Current  int    `json:"current"`
}

type PackageListOutput struct {
	Data []model.PackageHistory `json:"data"`
	Total int64 `json:"total"`
}
//包列表
func (s *packageServer) PackList(ctx context.Context, inputData string, outputData *DataOutput) error {
	//获取参数
	var InputData PackageListInput
	var outData DataOutput
	outData.Code = 200
	if err := json.Unmarshal([]byte(inputData), &InputData); err != nil {
		log.Print("PackList 参数错误-" + err.Error())
		outData.Message = "PackList 参数错误-" + err.Error()
		return err
	}

	var (
		PackageData []model.PackageHistory
		Total       int64
	)
	db.NewDb().Model(&model.PackageHistory{}).Order("created_at desc").Find(&PackageData)
	var res PackageListOutput
	res.Total = Total
	res.Data = PackageData
	jsonData, _ := json.Marshal(res)
	outData.Body = string(jsonData)
	*outputData = outData
	return nil
}

//进程列表
func (s *packageServer) ProcessList(ctx context.Context, inputData string, outputData *DataOutput) error {
	var outData DataOutput
	outData.Code = 200
	res := process2.NewProcess2().GetAllProcess()
	jsonData, _ := json.Marshal(res)
	outData.Body = string(jsonData)
	*outputData = outData
	return nil
}

type PackageUpdateInput struct {
	ID int `json:"id,omitempty"`
}

//发送包更新
func (s *packageServer) PackUpdate(ctx context.Context, inputData string, outputData *DataOutput) error {
	//获取参数
	var InputData PackageUpdateInput
	var outData DataOutput
	outData.Code = 200
	if err := json.Unmarshal([]byte(inputData), &InputData); err != nil {
		log.Print("PackList 参数错误-" + err.Error())
		outData.Message = "PackList 参数错误-" + err.Error()
		return err
	}

	var PackageData model.PackageHistory
	err := db.NewDb().Model(&model.PackageHistory{}).Where("ID =?", InputData.ID).Take(&PackageData).Error
	if err != nil {
		outData.Message = "查询更新包数据失败-" + err.Error()
		return err
	}


	if !util.FileExists(PackageData.FilePath) {
		outData.Message = "版本包文件不存在"
		return errors.New("版本包文件不存在")
	}
	doPackageDispense(PackageData.Name, PackageData.Version, PackageData.FilePath)

	db.NewDb().Model(&model.PackageHistory{}).Where("name = ?", PackageData.Name).Update("current", 0)
	PackageData.Status = 2
	PackageData.Current = 1
	db.NewDb().Model(&model.PackageHistory{}).Where("ID = ?", PackageData.ID).Updates(PackageData)
	outData.Body = "{}"
	*outputData = outData
	return nil
}

func doPackageDispense(name, version, packagePath string) error {
	var packageConfig model.Package
	err := db.NewDb().Where("name = ?", name).Take(&packageConfig).Error
	if err != nil{
		return err
	}
	clientList := make([]model.Client, 0)
	if packageConfig.Servers != ""{
		for _,s := range strings.Split(packageConfig.Servers, ","){
			clientList = append(clientList, model.Client{
				Host: s,
			})
		}
	}else{
		db.NewDb().Find(&clientList)
	}

	for _, item := range clientList {
		d, _ := client.NewPeer2PeerDiscovery(item.Host+":"+strconv.Itoa(Port), "")
		opt := client.DefaultOption
		opt.SerializeType = protocol.JSON
		xclient := client.NewXClient(ServerName, client.Failtry, client.RandomSelect, d, opt)
		var reply DataOutput
		params := map[string]interface{}{
			"name":         name,
			"version":      version,
			"path": packagePath,
			"hash":util.GetFilehash(packagePath),
			"public_path":packageConfig.Path,
			"type":packageConfig.Type,
			"args":packageConfig.Args,
		}
		paramsStr, _ := json.Marshal(params)
		xclient.Call(context.Background(), "package.on.update", string(paramsStr), &reply)
	}
	return nil
}

type PackageChangeInput struct {
	Name    string `json:"name,omitempty" name:"包名称(必填)" desc:"包名称(必填)" validate:"required"`
	Version string `json:"version,omitempty" name:"包版本(必填)" desc:"包版本(必填)" validate:"required"`
	Path    string `json:"path,omitempty" name:"包地址(必填)" desc:"包地址(必填)" validate:"required"`
	Hash    string `json:"hash,omitempty" name:"包哈希" desc:"包哈希" validate:"required"`
	PublicPath    string `json:"public_path,omitempty" name:"安装路径" desc:"包哈希" validate:"required"`
	Type    int `json:"type,omitempty" name:"包类型" desc:"包类型" validate:"required"`
	Args    string `json:"args,omitempty" name:"启动参数" desc:"启动参数" validate:"required"`

}

//接受包更新
func (s *packageServer) PackOnUpdate(ctx context.Context, inputData string, outputData *DataOutput) error {
	//获取参数
	var InputData PackageChangeInput
	var outData DataOutput
	outData.Code = 200
	if err := json.Unmarshal([]byte(inputData), &InputData); err != nil {
		log.Print("PackOnUpdate 参数错误-" + err.Error())
		outData.Message = "PackOnUpdate 参数错误-" + err.Error()
		return errors.New("PackOnUpdate 参数错误-" + err.Error())
	}
	//var configData config.Package
	//for _, cfg := range config.ConfigData.Packages {
	//	if cfg.Name == InputData.Name {
	//		configData = cfg
	//	}
	//}
	//if configData.Name == ""{
	//	processList := process2.NewProcess2().GetAllProcess()
	//	for _,p :=range processList {
	//		if p.Name == InputData.Name{
	//			hash := util.GetFilehash(p.ProcessFile)
	//			if hash != InputData.Hash{
	//				 configData.Name = InputData.Name
	//				 configData.Args = p.Args
	//				 configData.Path = p.ProcessFile
	//				 configData.Type = 1
	//			}else {
	//				break
	//			}
	//		}
	//	}
	//}
	//if configData.Name == "" {
	//	return nil
	//}
	var currentPack model.PackageCurrent
	if InputData.Type == 2{
		nameArr := strings.Split(InputData.Name, ".")
		if nameArr[len(nameArr) -1] != "zip"{
			InputData.Name += ".zip"
		}
	}
	err := db.NewDb().Model(&model.PackageCurrent{}).Where("name = ?", InputData.Name).Take(&currentPack).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return err
	}
	currentFileHash := util.GetFilehash(currentPack.FilePath)
	if err == nil && InputData.Hash == currentFileHash{
		fmt.Println("文件数据未变不进行更新操作")
		return err
	}
	currentPack.Version = InputData.Version
	currentPack.Name = InputData.Name
	currentPack.FilePath = strings.TrimRight(InputData.PublicPath, string(os.PathSeparator)) + string(os.PathSeparator) + InputData.Name

	go func(configData PackageChangeInput, currentPack model.PackageCurrent) {

		FilePath := currentPack.FilePath
		//if configData.Type == 2 { //先存放临时目录
		//	FilePath = os.TempDir() + "/" + configData.Name
		//
		//}

		if configData.Type == 1{
			process2.NewProcess2().Stop(configData.Name)
			time.Sleep(2 *time.Second)
		}

		var err error
		if config.ConfigData.RpcMaster == config.ConfigData.RpcServer {
			err = util.CopyFile(configData.Path, FilePath)
		} else {
			_, err = util.CopyRemoteFile(configData.Path, FilePath, config.ConfigData.RpcMaster, config.ConfigData.Ssh.User, config.ConfigData.Ssh.Pwd)
		}
		if err != nil {
			log.Print("PackOnUpdate 获取包失败-" + err.Error())
			return
		}
		db.NewDb().Model(&model.PackageCurrent{}).Save(&currentPack)
		//util.ClearDir(configData.PublicPath)

		if configData.Type == 2 {
			//util.CopyFile(FilePath, currentPack.FilePath)
			util.Unzip(currentPack.FilePath)
		} else {
			process2.NewProcess2().Start(FilePath, strings.Split(configData.Args, " "), "")

		}
	}(InputData, currentPack)
	*outputData = outData
	return nil
}

type ClientRegisterInput struct {
	Host string `json:"host,omitempty" name:"包名称(必填)" desc:"包名称(必填)" validate:"required"`
}



//日志文件列表
func (s *packageServer) LogFiles(ctx context.Context, inputData string, outputData *DataOutput) error {
	var outData DataOutput
	outData.Code = 200

	file, _ := os.Open(config.ConfigData.Logs)
	files, err := file.ReadDir(-1)

	if err != nil {
		outData.Message = "查询失败-" + err.Error()
	}

	filesData := make([]string, 0)
	for _, item := range files {
		filesData = append(filesData, item.Name())
	}
	filesDataStr,_ := json.Marshal(filesData)
	outData.Body = string(filesDataStr)
	*outputData = outData
	return nil
}

type ProcessLogInput struct {
	FileName string `json:"file_name"`
	Seek int64 `json:"seek"`
	PageSize int `json:"page_size"`
	Auto string `json:"auto"`
	LogType string `json:"log_type"`
	ProcessName string `json:"process_name"`
}

type ProcessLogOutput struct {
	Data string `json:"data"`
	Seek int64 `json:"seek"`
	Total int64 `json:"total"`
}

func (s *packageServer)ProcessLog(ctx context.Context, inputData string, outputData *DataOutput)error{
	//获取参数
	var InputData ProcessLogInput
	var outData DataOutput
	outData.Code = 200
	if err := json.Unmarshal([]byte(inputData), &InputData); err != nil {
		log.Print("PackOnUpdate 参数错误-" + err.Error())
		outData.Message = "PackOnUpdate 参数错误-" + err.Error()
		return errors.New("PackOnUpdate 参数错误-" + err.Error())
	}
	logFile := ""
	if InputData.FileName == "" && InputData.ProcessName != ""{
		process := process2.NewProcess2().GetProcess(InputData.ProcessName)
		if process == nil{
			outData.Message = "进程不存在"
			return nil
		}
		if InputData.LogType == process2.ErrorLogType{
			logFile = process.ErrorLog
		}else{
			logFile = process.Log
		}
	}else if InputData.FileName == ""{
		outData.Message = "日志文件名为空"
		return nil
	}else{
		logFile = config.ConfigData.Logs + string(os.PathSeparator)  + InputData.FileName
	}

	file, err1:= os.Open(logFile)
	if err1 != nil{
		outData.Message = "日志文件打开失败-"+ err1.Error()
		return nil
	}
	defer file.Close()
	fileinfo, _ := file.Stat()

	fileSize := fileinfo.Size()
	seekNum := InputData.Seek
	if InputData.PageSize <= 0{
		InputData.PageSize = 1024 * 1024
	}
	if  fileSize < seekNum{
		seekNum = 0
	}
	data := make([]byte, InputData.PageSize)
	//if InputData.Auto != ""{
	//	file.Seek(seekNum, 0)
	//}else{
	//	file.Seek(seekNum, 2)
	//}
	file.Seek(seekNum, 0)
	realNum ,_ := file.Read( data)


	res := ProcessLogOutput{
		 Data: string(data[:realNum]),
		 Seek: seekNum + int64(realNum),
		 Total: fileSize,
	}
	jsonData, _ := json.Marshal(res)
	outData.Body = string(jsonData)
	*outputData = outData
	return nil
}



//客户端注册
func (s *packageServer) ClientRegister(ctx context.Context, inputData string, outputData *DataOutput) error {
	//获取参数
	var InputData ClientRegisterInput
	var outData DataOutput
	outData.Code = 200
	if err := json.Unmarshal([]byte(inputData), &InputData); err != nil {
		log.Print("ClientRegister 参数错误-" + err.Error())
		outData.Message = "ClientRegister 参数错误-" + err.Error()
		return errors.New("ClientRegister 参数错误-" + err.Error())
	}
	id := setClient(InputData.Host)
	if id < 1 {
		outData.Message = "ClientRegister 注册失败"
	}
	*outputData = outData
	return nil
}

func NewRpcServer() error {
	if config.ConfigData.RpcServer == ""{
		return nil
	}
	s := server.NewServer()
	s.Plugins.Add(serverplugin.NewMetricsPlugin(metrics.DefaultRegistry))
	s.Plugins.Add(serverplugin.NewReqRateLimitingPlugin(time.Millisecond*10, 1, true))
	ps := new(packageServer)
	if config.ConfigData.RpcMaster == config.ConfigData.RpcServer { //主服务器时注册上传 更新方法

		s.EnableFileTransfer(UploadPath, server.NewFileTransfer(
			fmt.Sprintf(`%v:%v`,
				config.ConfigData.RpcMaster,
				UploadPort,
			),
			ps.UploadPack,
			nil,
			1000,
		))
		err := s.RegisterFunctionName(ServerName, "package.list", ps.PackList, "")
		if err != nil {
			fmt.Println(err)
		}
		s.RegisterFunctionName(ServerName, "package.update", ps.PackUpdate, "")
		s.RegisterFunctionName(ServerName, "client.register", ps.ClientRegister, "")
	}
	s.RegisterFunctionName(ServerName, "package.on.update", ps.PackOnUpdate, "")
	s.RegisterFunctionName(ServerName, "log.files", ps.LogFiles, "")
	s.RegisterFunctionName(ServerName, "process.log", ps.ProcessLog, "")
	s.RegisterFunctionName(ServerName, "process.list", ps.ProcessList, "")
	fmt.Println(fmt.Sprintf(`%v:%v`,
		config.ConfigData.RpcServer,
		Port,
	))
	err := s.Serve("tcp", fmt.Sprintf(`%v:%v`,
		config.ConfigData.RpcServer,
		Port,
	))
	if err != nil {
		panic(err.Error())
	}
	return err
}

func setClient(host string) uint {
	var client model.Client
	if err := db.NewDb().Model(&model.Client{}).Where("host = ?", host).Take(&client).Error; err == gorm.ErrRecordNotFound {
		client.Host = host
		client.Status = 1
		db.NewDb().Model(&model.Client{}).Create(&client)
	}
	return client.ID
}
